import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';

///Flutter 事件处理流程,hit test (命中测试,事件分发)
///

class PointerDownListenerRoute extends StatelessWidget {
  const PointerDownListenerRoute({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    print('down build');
    return Listener(
      behavior: HitTestBehavior.translucent,
      child: Text('Click me'),
      onPointerDown: (e) => print('down'),
    );
  }
}

class WaterMaskTest extends StatelessWidget {
  const WaterMaskTest({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        // ///场景一
        // wDecoratedBox("我是上层的控件", Colors.white, 200),
        // ///场景二
        // wGestureDetector("我是手势上层===的控件", Colors.white, 200),
        // TextButton(onPressed: (){  print("我是手势上层按钮");}, child: Text("SAFAF")),
        // wGestureDetector("我是手势上层==1=的控件", Colors.white, 200),

        ///场景三
        // //Container hittest 返回true会截断事件
        // wChild(1, "Container我是外层1的控件", Colors.white, 200),
        // wChild(2, "Container我是外层2的控件", Colors.white, 200),
        // // //SizeBox--会有穿透
        // wSizeBoxChild(1, "SizeBox我是外层1的控件", Colors.white, 200),
        // //这个sizebox为子空间控件 设置behavior translucent会有一层穿透
        // wSizeBoxChild(2, "SizeBox我是外层2的控件", Colors.white, 200),
        //
        //
        // ///场景四--下面我们定义一个可以拦截 hitTest 各个过程的 HitTestBlocker 组件。
        HitTestBlocker(child:  wChild(1, "HitTestBlocker我是外层1的控件", Colors.white, 200)),
        HitTestBlocker(child:  wChild(1, "HitTestBlocker我是外层2的控件", Colors.white, 200)),

        ///场景五--
        ///可以猜一下点击后会输出什么？答案是只会输出 2 ！
        ///这是因为虽然 Stack 的两个子组件都会参与且通过命中测试，但是 GestureDetector 会在事件分发阶段来决定是否响应事件（而不是命中测试阶段）
        // HitTestBlocker(child:  wGestureDetector( "HitTestBlocker我是外层1的控件 GestureDetector", Colors.white, 200)),
        // HitTestBlocker(child:  wGestureDetector( "HitTestBlocker我是外层2的控件 GestureDetector", Colors.white, 200)),
      ],
    );
  }

  HitTestBehavior getBehavior(int index) {
    if (index == 2) {
      return HitTestBehavior.translucent;
    }
    return HitTestBehavior.opaque;
  }

  HitTestBehavior getBehaviorSizeBox(int index) {
    ///测试场景一、两个都返回 HitTestBehavior.translucent，则会打印三个日志（）
    ///I/flutter (15506): SizeBox我是外层2的控件
    // I/flutter (15506): SizeBox我是外层1的控件
    // I/flutter (15506): Container我是外层2的控件

    // return HitTestBehavior.translucent;

    ///测试场景二、index =2 返回 HitTestBehavior.translucent，index == 1返回 HitTestBehavior.opaque 则会打印两个日志
    ///I/flutter (15506): SizeBox我是外层2的控件
    // I/flutter (15506): SizeBox我是外层1的控件

    if (index == 2) {
      return HitTestBehavior.translucent;
    }
    return HitTestBehavior.opaque;

    ///测试场景二、index =2 返回 HitTestBehavior.opaque，index == 1返回 HitTestBehavior.translucent 则会打印1个日志
    ///I/flutter (15506): SizeBox我是外层2的控件,原因：
    ///if (size.contains(position)) {
    ///
    /// Listener 控件的 hitTestSelf(position) 返回true，这样兄弟节点就不会遍历了，
    /// 只加入 wSizeBoxChild（2）
    //       hitTarget = hitTestChildren(result, position: position) || hitTestSelf(position);
    //       if (hitTarget || behavior == HitTestBehavior.translucent)
    //         result.add(BoxHitTestEntry(this, position));


    // if (index == 1) {
    //   return HitTestBehavior.translucent;
    // }
    // return HitTestBehavior.opaque;
  }

  ///wChild 方法介绍里面是一个 Container
  //答案是只会打印一个 'Container我是底层2的控件'，
  // 原因是，Stack 先遍历第二个子节点 Listener ，
  // 因为 Container 的 hitTest 会返回 true（实际是 Container 内部的 DecoratedBox ），
  // 所以 Listener 的 hitTestChildren 会返回 true，最终 Listener 的hitTest 也会返回true，
  // 所以第一个子节点将不会收到事件。
  ///那如果我们将 Listener 的 behavior 属性指定为 opaque 或 translucent 呢？
  ///其实结果还是一样的，
  ///因为只要 Container 的 hitTest 会返回 true，
  ///最终Listener 的 hitTestChildren 就会返回 true，第一个节点就不会再进行命中测试。
  Widget wChild(int index, String hint, color, double size) {
    return Listener(
      behavior: getBehavior(index),
      onPointerDown: (e) => print(hint),
      child: Container(
        width: size,
        height: size,
        color: Color(0xFF10E5AA),
      ),
    );
  }

  ///SizedBox 没有子元素，当它被点击时，--RenderConstrainedBox--RenderProxyBox--RenderBox
  ///没有子元素时 RenderBox中的 hitTestChildren(result, position: position) || hitTestSelf(position) 返回值都为false
  ///它的 hitTest 就会返回 false，此时 Listener 的 behavior 设置为 opaque 和translucent
  ///就会有区别（见注释）。
  Widget wSizeBoxChild(int index, String hint, color, double size) {
    return Listener(
      behavior: getBehaviorSizeBox(index),
      onPointerDown: (e) => print(hint),
      child: SizedBox(
        width: size,
        height: size,
      ),
    );
  }

  Widget wGestureDetector(String hint, color, double size) {
    return Container(
      child: GestureDetector(
        onTap: () {
          print(hint);
        },
        behavior: HitTestBehavior.opaque,
      ),
      color: Color(0xFF6910E5),
    );
  }

  Widget wDecoratedBox(String hint, color, double size) {
    return DecoratedBox(
      decoration: BoxDecoration(
          gradient: LinearGradient(
              colors: [Colors.red, Colors.orange.shade700]), //背景渐变
          borderRadius: BorderRadius.circular(3.0), //3像素圆角
          boxShadow: [
            //阴影
            BoxShadow(
                color: Color(0x8ADB6516),
                offset: Offset(2.0, 2.0),
                blurRadius: 4.0)
          ]),
    );
  }
}



///下面我们定义一个可以拦截 hitTest 各个过程的 HitTestBlocker 组件。
///原理 包裹一层将自身等级hitTest返回 false，致使兄弟级别能够有机会参与命中测试
class HitTestBlocker extends SingleChildRenderObjectWidget {
  HitTestBlocker({
    Key? key,
    this.up = true,
    this.down = false,
    this.self = false,
    Widget? child,
  }) : super(key: key, child: child);

  /// up 为 true 时 , `hitTest()` 将会一直返回 false.
  final bool up;

  /// down 为 true 时, 将不会调用 `hitTestChildren()`.
  final bool down;

  /// `hitTestSelf` 的返回值
  final bool self;

  @override
  RenderObject createRenderObject(BuildContext context) {
    return RenderHitTestBlocker(up: up, down: down, self: self);
  }

  @override
  void updateRenderObject(
      BuildContext context, RenderHitTestBlocker renderObject) {
    renderObject
      ..up = up
      ..down = down
      ..self = self;
  }
}

class RenderHitTestBlocker extends RenderProxyBox {
  RenderHitTestBlocker({this.up = true, this.down = true, this.self = true});

  bool up;
  bool down;
  bool self;

  @override
  bool hitTest(BoxHitTestResult result, {required Offset position}) {

    bool hitTestDownResult = false;

    if (!down) {
      //当前容器的子类消费
      hitTestDownResult = hitTestChildren(result, position: position);
    }

    bool pass =
        hitTestSelf(position) || (hitTestDownResult && size.contains(position));
    print("RenderHitTestBlocker：$pass");
    if (pass) {
      result.add(BoxHitTestEntry(this, position));
    }
    //返回false的话，当前容器的兄弟有机会参与命中
    return !up && pass;
  }

  @override
  bool hitTestSelf(Offset position) => self;
}